Ga naar hoofdinhoud

Advanced concepts

Automatic Semicolon Insertion (ASI)

JavaScript voegt in sommige gevallen automatisch puntkomma's (;) toe waar deze worden weggelaten door de programmeur, om syntaxisfouten te voorkomen. Dit kan echter tot onverwacht gedrag leiden als de programmeur vertrouwt op deze automatische toevoegingen.

Voorbeeld

// Dit werkt goed dankzij ASI
let x = 5
let y = x + 10

// Maar dit kan misgaan door ASI:
return
{
name: "John"
}

// Het wordt geïnterpreteerd als:
return; // einde van de functie
{
name: "John" // losstaand blok dat nooit wordt uitgevoerd
}

TIP: ASI kan fouten veroorzaken, dus het expliciet toevoegen van puntkomma’s wordt aangeraden.

ASI documentatie

Double (==) vs Triple Equality (===)

Typecohercitite is een mechanisme in javascript waar automatisch het type van 1 variabele gewijzigd wordt om te voldoen aan een bepaalde bewerking. Je ziet dit bijvoorbeeld ook als je een number en een string bij elkaar optelt, dan is het resultaat altijd een string. Dit is omdat het nummer wordt omgezet naar een string, zodat er string concatinatie kan uitgevoerd worden.

const string = "my string" + 5;
console.log(string) // logt een string "mystring5"

Dubbele gelijkheid (==) voert typecohercitite uit. Het converteert beide waarden naar hetzelfde type voordat het vergelijkt. Triple gelijkheid (===) vergelijkt zowel de waarde als het type zonder conversie.

Voorbeeld

5 == "5";  // true (type coercion)
5 === "5"; // false (verschillende types)

null == undefined; // true (beide worden gezien als "empty" waarden)
null === undefined; // false (verschillende types)

TIP: Gebruik altijd === om onverwachte resultaten te vermijden.

Hoisting

Hoisting betekent dat variabelen en functies naar de top van hun scope worden "verplaatst" tijdens de compileertijd, waardoor ze eerder beschikbaar zijn dan verwacht.

Voorbeeld

console.log(x); // undefined (hoisting van var x)
var x = 10;

greet(); // "Hello!" (functies worden volledig gehoist)
function greet() {
console.log("Hello!");
}

Het feit dat x hier als undefined gelogd wordt is misschien raar in de context van hoisting maar dat is omdat alleen de declaraties worden gehoist, niet de initialisaties. Dat betekent dat var x; naar boven gehoist wordt, maar x=10 blijft op dezelfde plaats staan. Op het moment dat je console.log(x) doet is x dus nog niet gelijk aan 10.

Met let en const worden variabelen wel gehoist, maar kun je ze niet gebruiken voordat ze daadwerkelijk gedeclareerd zijn (Temporal Dead Zone). Met bovenstaand voorbeeld bijvoorbeeld:

console.log(x); // ReferenceError: cannot access x before initialisation
let x = 10;

Dit is omdat let x wel naar boven gehoist wordt, maar net zoals bij var hierboven heeft het nog geen waarde wanneer de console.log bereikt wordt. In het geval van let en const wordt hier dus effectief een error gegooid.

TIP: Gebruik zoveel mogelijk const en let omdat het beter beschermt tegen hoisting en de mogelijk ongewenste neveneffecten.

Scopes

Een scope bepaalt de zichtbaarheid van variabelen en functies. In JavaScript zijn er voornamelijk drie soorten scopes:

  1. Global scope: variabelen en functies gedefinieerd buiten functies of blokken.
  2. Function scope: variabelen gedefinieerd binnen een functie zijn alleen zichtbaar binnen die functie.
  3. Block scope (bij gebruik van let of const): variabelen gedefinieerd binnen een blok {} zijn alleen zichtbaar binnen dat blok.

Voorbeeld

let x = 10;  // Global scope

function test() {
let y = 20; // Function scope
if (true) {
let z = 30; // Block scope
}
console.log(z); // Error: z is not defined
}

Block scopes zijn alleen van toepassing op let en const. Dat betekent dat als je het voorbeeld hierboven zou herschrijven om in plaats van let z, var z te gebruiken, het gedrag anders zou zijn:

function example() {
if (true) {
var x = 5; // x is beschikbaar in de hele function scope
}
console.log(x); // 5
}
example();

TIP: Zorg ervoor dat de scope van je variabele altijd zo strikt mogelijk gezet is. Als een variabele alleen maar binnen een functie gebruikte moet worden, dan definieer je die in de functie en niet globaal.

Strict Mode

Strict mode in JavaScript dwingt strengere regels af om mogelijke fouten en onveilige acties te voorkomen. Dit kan helpen om betere code te schrijven. Wanneer je een framework gebruikt of in TypeScript ontwikkelt wordt strict mode typisch afgedwongen.

Voorbeeld

"use strict";

x = 5; // Error: x is not defined (in non-strict mode, dit zou global zijn)

Voeg "use strict"; toe aan het begin van een bestand of functie om strict mode in te schakelen.

Documentatie van strict mode

Closures

Wat zijn Closures?

In JavaScript zijn closures een krachtig en belangrijk concept dat ontstaat wanneer een functie toegang heeft tot zijn eigen scope, de scope van de omringende functie, en de globale scope. Dit zelfs nadat de omringende functie is voltooid. Dit betekent dat een closure de variabelen en parameters van de omringende functie kan "onthouden" en gebruiken, zelfs nadat die functie is uitgevoerd.

Hoe werkt een Closure?

Wanneer een functie binnen een andere functie wordt gedefinieerd, creëert deze een closure. De innerlijke functie kan toegang krijgen tot de variabelen van de buitenste functie, zelfs nadat de buitenste functie is teruggegeven.

Voorbeeld van een Closure

Hier is een eenvoudig voorbeeld om het concept van closures te illustreren:

function makeCounter() {
let count = 0; // Een variabele in de scope van de buitenste functie

return function() { // Dit is de closure
count++; // De closure kan de variabele 'count' gebruiken
return count; // Retourneert de bijgewerkte waarde
};
}

const counter = makeCounter(); // Hier wordt de closure aangemaakt
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

In dit voorbeeld:

  • De functie makeCounter maakt een variabele count die is geïsoleerd binnen de scope van die functie.
  • De innerlijke functie (closure) heeft toegang tot count, ook al is makeCounter al voltooid.
  • Elke keer dat counter() wordt aangeroepen, wordt de waarde van count met 1 verhoogd.

Praktisch Nut van Closures

Closures hebben verschillende praktische toepassingen, waaronder:

  1. Data Encapsulation:

    • Closures stellen je in staat om gegevens te verbergen en alleen via functies toegankelijk te maken. Dit is handig voor het creëren van een soort privédatabase.
    • Voorbeeld: Dit kan worden gebruikt om variabelen in een module te verbergen, waardoor ze niet rechtstreeks toegankelijk zijn vanuit andere delen van de code.
  2. Functionele Programmeren:

    • Je kunt functies maken die andere functies retourneren, wat nuttig is voor functionele programmering. Dit is te zien in patronen zoals het maken van higher-order functions.
  3. Asynchrone Programmeren:

    • Closures zijn ook nuttig bij asynchrone operaties, zoals callbacks, omdat ze de context (variabelen) behouden van het moment waarop ze zijn aangemaakt, zelfs als de oorspronkelijke functie al is voltooid.
  4. Currying:

    • Closures maken currying mogelijk, een techniek waarbij een functie meerdere argumenten in verschillende stappen kan ontvangen.

Voorbeeld van Data Encapsulation met Closures

Hier is een voorbeeld dat laat zien hoe closures kunnen worden gebruikt voor gegevensverzamelingen:

function createPerson(name) {
let age = 0; // Deze variabele is privé

return {
getName: function() {
return name; // Toegang tot de naam
},
getAge: function() {
return age; // Toegang tot de leeftijd
},
celebrateBirthday: function() {
age++; // Verhoogt de leeftijd
return age;
}
};
}

const person = createPerson('Alice');
console.log(person.getName()); // Alice
console.log(person.getAge()); // 0
console.log(person.celebrateBirthday()); // 1

Modules

JavaScript-modules laten je toe om code in afzonderlijke bestanden te splitsen en ze vervolgens te importeren of exporteren. Dit verbetert de modulariteit en herbruikbaarheid van code.

Voorbeeld

// In file math.js
export function add(a, b) {
return a + b;
}

// In another file
import { add } from './math.js';
console.log(add(2, 3)); // 5

Modules worden vaak gebruikt in moderne JavaScript-projecten, vooral met tools zoals ES6 en bundlers zoals Webpack.

Revealing module pattern

Het Revealing Module Pattern is een ontwerpprincipe dat in JavaScript wordt gebruikt om een schone en georganiseerde manier van het structureren van code te bieden. Het stelt ontwikkelaars in staat om een module te maken met een duidelijke interface, terwijl de interne implementatie verborgen blijft. Dit helpt bij het voorkomen van conflicten in de globale namespace en bevordert de encapsulatie van gegevens.

const myModule = (function() {
// Privé variabele
let privateVar = "Ik ben privé";

// Privé functie
function privateMethod() {
console.log(privateVar);
}

return {
// Openbare functie
publicMethod: function() {
privateMethod(); // Toegang tot de privéfunctie
},
// Openbare variabele
publicVar: "Ik ben openbaar"
};
})();

myModule.publicMethod(); // Output: Ik ben privé
console.log(myModule.publicVar); // Output: Ik ben openbaar
// console.log(myModule.privateVar); // TypeError: privateVar is not defined

IIFE (Immediately Invoked Function Expression)

Een IIFE is een functie die onmiddellijk na het definiëren wordt uitgevoerd. Het creëert een scope voor variabelen zodat ze niet de globale scope vervuilen.

Voorbeeld

(function() {
let x = 10;
console.log(x); // 10
})();

console.log(x); // Error: x is not defined (x is niet beschikbaar buiten de IIFE)

Gebruik: IIFE’s worden vaak gebruikt om code te isoleren en te beschermen tegen ongewenste toegang van buitenaf.